#include "Resources.h"
#include "Sound.h"
#include "Listener.h"
#include "Structure.h"

#include "OpenAL/al.h"
#include "OpenAL/alc.h"
#include "OpenAL/alut.h"
#include "OpenAL/xram.h"
#include "OpenAL/efx.h"
#include "OpenAL/EFX-Util.h"

#include <map>
#include <string>
#include <vector>
#include <deque>
#include <iterator>

#include <boost/thread/thread.hpp>
#include <boost/thread/recursive_mutex.hpp>
#include <boost/thread/xtime.hpp>

#define MAX_SOURCES 100

#pragma once

namespace UniverseSound
{
	class SoundManager : public Ogre::Singleton<SoundManager>
	{
	public:
		SoundManager();
		~SoundManager();
		static SoundManager& getSingleton();
		static SoundManager *getSingletonPtr();

		bool initialize(const std::string& device = "Device");
		int createSourcePool();
		void setMasterVolume(ALfloat vol);
		ALfloat getMasterVolume();
		SoundClip *createCustomSound(const std::string& name, const std::string& file, bool stream = false, bool loop = false, bool prebuffer = false);
		SoundClip *createCustomSound(Ogre::SceneManager& sceneManager, const std::string& name, const std::string& file, bool stream = false, bool loop = false, bool prebuffer = false);
		SoundClip *createStreamSound(const std::string& name, const std::string& file, bool loop = false);
		SoundClip *createStreamSound(const Ogre::SceneManager& sceneManager, const std::string& name, const std::string& file, bool loop = false);
		SoundClip *createStaticSound(const std::string& name, const std::string& file, bool loop = false);
		SoundClip *createStaticSound(const Ogre::SceneManager& sceneManager, const std::string& name, const std::string& file, bool loop = false);
		SoundClip *getSound(const std::string& name);
		Ogre::StringVector getSoundList();

		bool hasSound(const std::string& name);
		
		void playSound(const Ogre::String& soundName);
		void pauseSound(const Ogre::String& soundName);
		//void resumeSound(const Ogre::String& soundName);
		void stopSound(const Ogre::String& soundName);
		void destroySound(const Ogre::String& soundName);
		void stopAllSounds();
		void pauseAllSounds();
		void resumeAllSounds();
		void destroyAllSounds();

		bool isXRAMSupported();
		bool isEFXSupported();
		bool isEAXSupported();
		
		void updateBuffers();
		bool requestSoundSource(SoundClip *sound = 0);
		bool releaseSoundSource(SoundClip *sound = 0);
		void setDistanceModel(ALenum value);
		void setDopplerFactor(Ogre::Real factor = 1.f);
		void setSpeed(Ogre::Real speed = 363.f);
		Ogre::StringVector getDeviceList();
		Listener *getListener();
		int getNumSources();
		void processThreadedSounds();
		void update(Ogre::Real time=0.f);
		void setXRAMBuffer(ALsizei numBuffers, ALuint *buffers);
		void setXRAMBufferMode(ALenum mode);
		void setEFXDistanceUnits(Ogre::Real units = 3.3f);
		bool createEFXFilter(const std::string& name, ALint type, ALfloat gain = 1.0, ALfloat hfGain = 1.0);
		bool createEFXEffect(const std::string& name, ALint type, EAXREVERBPROPERTIES *properties = 0);
		bool setEFXSoundProperties(const std::string& name, Ogre::Real airAbsorption = 0.f, Ogre::Real roomRolloff = 0.f, Ogre::Real coneOuterHF = 0.f);
		bool setEFXEffectParam(const std::string& name, ALint type, ALenum attrib, ALfloat parameter);
		bool setEFXEffectParam(const std::string& name, ALint type, ALenum attrib, ALfloat *params = 0);
		bool setEFXEffectParam(const std::string& name, ALint type, ALenum attrib, ALint parameter);
		bool setEFXEffectParam(const std::string& name, ALint type, ALenum attrib, ALint *params = 0);
		int getNumEffectSlotsSupported();
		int getNumEffectSlotsCreated();
		bool createEFXSlot();
		bool attachEffect(const std::string& soundName, ALuint slot, const Ogre::String& effect = "", const Ogre::String& filter = "");
		bool attachFilter(const std::string& soundName, const Ogre::String& filter = "");
		bool detachEffect(const std::string& soundName, ALuint slotID);
		bool detachFilter(const std::string& soundName);
		bool isEffectSupported(ALint effectID);
		bool releaseSharedBuffer(const Ogre::String& name, ALuint& buffer);
		bool registerSharedBuffer(const Ogre::String& name, ALuint& buffer);
		void queueThreadedSound(SoundClip *sound = 0, ONLOAD_ACTION action = ONLOAD_PLAY);

		boost::recursive_mutex mMutex;

		static::boost::thread *mUpdateThread;
		static bool mShutDown;

		static void threadUpdate();

	//protected:
		void _destroySound(SoundClip *sound = 0);
		void _unregisterSound(SoundClip *sound = 0);
		ALuint _getSharedBuffer(const Ogre::String& soundName);

	private:
		//ALuint _getSharedBuffer(const Ogre::String& soundName);
		void _release();
		void _checkFeatureSupport();
		bool _checkEFXSupport();
		bool _checkXRAMSupport();
		void _determineAuxEffectSlots();
		ALuint _getEFXFilter(const std::string& name);
		ALuint _getEFXEffect(const std::string& name);
		ALuint _getEFXEffectSlot(int slotID = 0);
		bool _setEAXReverbProps(EFXEAXREVERBPROPERTIES *properties, ALuint uiEfect);
		bool _attachEffectToSlot(ALuint slot, ALuint effect);
		void _reactivateQueuedSounds();
		void _listAudioDevices();

		ALCdevice *mDevice;
		ALCcontext *mContext;

		SoundMap mSoundMap;
		ActiveSoundList mActiveSounds;
		FileOpenList mLoadedSounds;
		ActiveSoundList mPlayQueue;
		ActiveSoundList mPauseQueue;
		ActiveSoundList mStopQueue;
		ActiveSoundList mPausedSounds;
		ActiveSoundList mInactiveSounds;
		SourceList mSourcePool;
		FeatureList mEFXSupportList;
		SharedBufferList mSharedBuffers;

		static SoundManager *mInstance;
		ALCchar *mDeviceStrings;
		int mNumSources;

		struct _sortNearToFar;
		struct _sortFarToNear;

		// Effect objects
		LPALGENEFFECTS alGenEffects;
		LPALDELETEEFFECTS alDeleteEffects;
		LPALISEFFECT alIsEffect;
		LPALEFFECTI alEffecti;
		LPALEFFECTIV alEffectiv;
		LPALEFFECTF alEffectf;
		LPALEFFECTFV alEffectfv;
		LPALGETEFFECTI alGetEffecti;
		LPALGETEFFECTIV alGetEffectiv;
		LPALGETEFFECTF alGetEffectf;
		LPALGETEFFECTFV alGetEffectfv;

		//Filter objects
		LPALGENFILTERS alGenFilters;
		LPALDELETEFILTERS alDeleteFilters;
		LPALISFILTER alIsFilter;
		LPALFILTERI alFilteri;
		LPALFILTERIV alFilteriv;
		LPALFILTERF alFilterf;
		LPALFILTERFV alFilterfv;
		LPALGETFILTERI alGetFilteri;
		LPALGETFILTERIV alGetFilteriv;
		LPALGETFILTERF alGetFilterf;
		LPALGETFILTERFV alGetFilterfv;

		// Auxiliary slot object
		LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots;
		LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots;
		LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot;
		LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti;
		LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv;
		LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf;
		LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv;
		LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti;
		LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv;
		LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf;
		LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv;

		typedef ALboolean (__cdecl *LPEAXSETBUFFERMODE)(ALsizei n, ALuint *buffers, ALint value);
		typedef ALenum (__cdecl *LPEAXGETBUFFERMODE)(ALuint buffer, ALint *value);

		LPEAXSETBUFFERMODE mEAXSetBufferMode;
		LPEAXGETBUFFERMODE mEAXGetBufferMode;

		bool mEAXSupport;
		int mEAXVersion;
		bool mXRAMSupport;
		bool mEFXSupport;

		EffectList *mFilterList;
		EffectList *mEffectList;
		std::vector<ALuint>* mEffectSlotList;

		ALint mNumEffectSlots;
		ALint mNumSendsPerSource;
		ALenum	mXRAMSize,
				mXRAMFree,
				mXRAMAuto,
				mXRAMHardware,
				mXRAMAccessible,
				mCurrentXRAMMode;

		ALint	mXRAMSizeMB,
				mXRAMFreeMB;

		Listener *mListener;

	};
}